// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/*
Library for defining subcommands in a structured way.
*/

// Package subcommand provides a simple framework for defining subcommands.
package subcommand

import (
	"flag"
	"fmt"
	"os"
	"sort"
	"text/tabwriter"
)

// Subcommand represents a single subcommand, with short and long versions of
// its help (usage) text, a function which adds command-line flags to a
// pre-existing flag.FlagSet, and a function which will be called to actually
// execute the subcommand.
type Subcommand struct {
	shortHelp string
	longHelp  string
	flagFn    func(*flag.FlagSet)
	runFn     func(*flag.FlagSet) error
}

// Help prints the short and long help messages, as well as the autogenerated
// flags documentation.
func (c *Subcommand) Help(flags *flag.FlagSet) {
	fmt.Printf("%s\n\n%s\n\n", c.shortHelp, c.longHelp)
	flags.PrintDefaults()
}

// InitFlags adds the subcommand's flags to a FlagSet, if flagFn is defined.
func (c *Subcommand) InitFlags(flags *flag.FlagSet) {
	if c.flagFn != nil {
		c.flagFn(flags)
	}
}

// Run executes the subcommand's runFn, if it is defined.
func (c *Subcommand) Run(flags *flag.FlagSet) error {
	if c.runFn != nil {
		return c.runFn(flags)
	}
	return nil
}

// Private registry holding a map of subcommands. Users of this library only
// have access to a default instance of the registry via the New and Tabulate
// functions, but tests can instantiate one directly.
type registry struct {
	subcommands map[string]*Subcommand
}

func (r *registry) new(name string, shortHelp string, longHelp string, flagFn func(*flag.FlagSet), runFn func(*flag.FlagSet) error) *Subcommand {
	if _, ok := r.subcommands[name]; ok {
		panic(fmt.Errorf("attempted to double-register subcommand %q", name))
	}
	ret := &Subcommand{shortHelp, longHelp, flagFn, runFn}
	r.subcommands[name] = ret
	return ret
}

func (r *registry) get(name string) *Subcommand {
	s, ok := r.subcommands[name]
	if !ok {
		return nil
	}
	return s
}

func (r *registry) tabulate() {
	colwriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	keys := make([]string, 0, len(r.subcommands))
	for key := range r.subcommands {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		fmt.Fprintf(colwriter, "\t%v\t%v\n", key, r.subcommands[key].shortHelp)
	}
	colwriter.Flush()
}

var defaultRegistry = &registry{make(map[string]*Subcommand)}

// New creates, registers, and returns a new Subcommand struct.
// Panics if you attempt to register a second command with the same name.
func New(name string, shortHelp string, longHelp string, flagFn func(*flag.FlagSet), runFn func(*flag.FlagSet) error) *Subcommand {
	return defaultRegistry.new(name, shortHelp, longHelp, flagFn, runFn)
}

// Get returns a Subcommand struct which has been previously registered.
func Get(name string) *Subcommand {
	return defaultRegistry.get(name)
}

// Tabulate prints out all registered Subcommand names and their
// short help strings.
func Tabulate() {
	defaultRegistry.tabulate()
}
